package git.soulbgm.config;

import git.soulbgm.common.filter.TokenFilter;
import git.soulbgm.common.filter.UrlPathMatchingFilter;
import git.soulbgm.common.realm.CustomRealm;
import git.soulbgm.common.realm.JwtRealm;
import git.soulbgm.common.resolver.UrlPermissionResolver;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.permission.PermissionResolver;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.mgt.SubjectDAO;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.IRedisManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.core.StringRedisTemplate;

import javax.servlet.Filter;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Shiro的配置类
 *
 * @author SoulBGM
 * @version 1.0
 * @date 2019/10/12 14:44
 */
@ConfigurationProperties(prefix = "config.shiro")
@PropertySource(value = "classpath:/config/config.properties")
@Configuration
public class ShiroConfig {

    @Autowired
    private CustomRealm realm;

    @Autowired
    private JwtRealm jwtRealm;

    @Autowired
    private IRedisManager redisManager;

    @Autowired
    private TokenConfig tokenConfig;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * session过期时间 默认30分钟
     */
    @Value("${server.session.timeout:30}")
    private int sessionTimeout;

    /**
     * 哈希算法名称（加密算法）
     */
    public final static String HASH_ALGORITHM_NAME = "md5";

    /**
     * 哈希迭代次数（加密次数）
     */
    public final static int HASH_ITERATIONS = 2;

    /**
     * 是否开启权限访问控制
     */
    private boolean permission;

    /**
     * 运行匿名访问的url 格式以/开头多个地址之间以逗号分隔 例:/login,/logout
     */
    private String anonUrl;

    public void setPermission(boolean permission) {
        this.permission = permission;
    }

    public void setAnonUrl(String anonUrl) {
        this.anonUrl = anonUrl;
    }

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");

        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("customAuthc", new UrlPathMatchingFilter());
        filterMap.put("tokenFilter", getTokenFilter());

        shiroFilterFactoryBean.setFilters(filterMap);

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //  authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
        filterChainDefinitionMap.put("/", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/token", "anon");
//        filterChainDefinitionMap.put("/logout", "anon");

        // swagger2 相关url
        filterChainDefinitionMap.put("/api", "anon");
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**", "anon");
        filterChainDefinitionMap.put("/v2/**", "anon");

        String[] split = anonUrl.split(",");
        for (String s : split) {
            filterChainDefinitionMap.put(s, "anon");
        }

        //主要这行代码必须放在所有权限设置的最后，不然会导致所有 url 都被拦截 剩余的都需要认证
//        filterChainDefinitionMap.put("/**", "customAuthc");
        filterChainDefinitionMap.put("/**", "tokenFilter,customAuthc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        // 将自定义的权限匹配器注入到自定义 Realm 中
        realm.setPermissionResolver(getPermissionResolver());
        // 将密码匹配器配置到自定义 Realm 中
        realm.setCredentialsMatcher(getHashedCredentialsMatcher());
        // 将凭证匹配配置为md5
        DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
        // 设置自定义的Realm
//        defaultSecurityManager.setRealm(realm);
        defaultSecurityManager.setRealms(Arrays.asList(realm, jwtRealm));
        /*
            Shiro提供了三个默认SessionManager实现
            1、DefaultSessionManager: DefaultSecurityManager 使用的默认实现，用于JavaSE环境；
            2、ServletContainerSessionManager: DefaultWebSecurityManager 使用的默认实现，用于Web环境，其直接使用Servlet容器的会话；
            3、DefaultWebSessionManager: 用于Web环境的实现，可以替代 ServletContainerSessionManager ，自己维护着会话，直接废弃了Servlet容器的会话管理。
         */
        // 设置SessionManager
        defaultSecurityManager.setSessionManager(getSessionManager());
        // 设置主体内容设置
        defaultSecurityManager.setSubjectDAO(getSubjectDAO());

        return defaultSecurityManager;
    }

    public TokenFilter getTokenFilter() {
        return new TokenFilter(tokenConfig, redisTemplate);
    }

    /**
     * 会话管理(Session管理)
     */
    public SessionManager getSessionManager() {
//        return new ServletContainerSessionManager();
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setCacheManager(getCacheManager());
        sessionManager.setSessionDAO(getSessionDAO());
        return sessionManager;
    }

    /**
     * Session管理
     */
    private SessionDAO getSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager);
        redisSessionDAO.setExpire(sessionTimeout * 60);
        redisSessionDAO.setSessionIdGenerator(new JavaUuidSessionIdGenerator());
        // redis 存储session的前缀 默认shiro:session:
        /*redisSessionDAO.setKeyPrefix("shiro:session:");*/
        return redisSessionDAO;
    }

    /**
     * 缓存管理
     */
    private CacheManager getCacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager);
        return redisCacheManager;
    }

    /**
     * 主体内容管理
     */
    private SubjectDAO getSubjectDAO() {
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        subjectDAO.setSessionStorageEvaluator(getSessionStorageEvaluator());
        return subjectDAO;
    }

    /**
     * 禁用session, 不保存用户登录状态。保证每次请求都重新认证。
     * 需要注意的是，如果用户代码里调用Subject.getSession()还是可以用session，如果要完全禁用，要配合下面的noSessionCreation的Filter来实现
     */
    private SessionStorageEvaluator getSessionStorageEvaluator() {
        DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
        // 为false则关闭shiro自带的session
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        return sessionStorageEvaluator;
    }

    /**
     * 自定义的权限匹配器 使用Url进行权限匹配
     */
    public PermissionResolver getPermissionResolver() {
        return new UrlPermissionResolver(permission);
    }

    /**
     * 凭证匹配器
     */
    public HashedCredentialsMatcher getHashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //散列算法:这里使用MD5算法
        hashedCredentialsMatcher.setHashAlgorithmName(HASH_ALGORITHM_NAME);
        //散列的次数，比如散列两次，相当于 md5(md5(""))
        hashedCredentialsMatcher.setHashIterations(HASH_ITERATIONS);
        return hashedCredentialsMatcher;
    }

    /**
     * *
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * *
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
     * * @return
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }


}
